// MSSpeechTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "sapi.h"
#include "sphelper.h"
#include <iostream>
#include "SAPINotifySink.h"
#include "StreamReceiver.h"
#include "StreamProvider.h"
#include "SFML/Audio/SoundBuffer.hpp"
#include "SFML/Audio/Sound.hpp"


//#include "SpStream.h"
typedef void __stdcall SPNOTIFYCALLBACK(WPARAM wParam, LPARAM lParam);

void __stdcall notifyCallback(WPARAM wParam, LPARAM lParam)
{
	ISpVoice * pVoice = (ISpVoice*)wParam;
	SPVOICESTATUS status;
	pVoice->GetStatus(&status, NULL);
	std::cout << status.ulInputWordLen;
}

HRESULT speechRecognize(StreamProvider* input, SAPINotifySink* srSink)
{
	HRESULT hr;
	CComPtr<ISpRecognizer> recognizer;
	hr = recognizer.CoCreateInstance(CLSID_SpInprocRecognizer);
	if(!SUCCEEDED(hr))
		return hr;

	CComPtr<ISpRecoContext> recoContext;
	hr = recognizer->CreateRecoContext(&recoContext);
	if(!SUCCEEDED(hr))
		return hr;

    const ULONGLONG ullInterest = SPFEI(SPEI_PHRASE_START) | SPFEI(SPEI_RECOGNITION) |
                                  SPFEI(SPEI_FALSE_RECOGNITION);
    hr = recoContext->SetInterest( ullInterest, ullInterest );
	if(!SUCCEEDED(hr))
		return hr;

	srSink->pRecoContext = recoContext;
	hr = recoContext->SetNotifySink(srSink);
	if(!SUCCEEDED(hr))
		return hr;

    // Retain recognized audio
    hr = recoContext->SetAudioOptions( SPAO_RETAIN_AUDIO, NULL, NULL );
	if(!SUCCEEDED(hr))
		return hr;

	CComPtr<ISpRecoGrammar> recoGrammar;
    hr = recoContext->CreateGrammar(0, &recoGrammar);
	if(!SUCCEEDED(hr))
		return hr;

    hr = recoGrammar->LoadDictation( NULL, SPLO_STATIC );
	if(!SUCCEEDED(hr))
		return hr;

	CComPtr<ISpStream> cpStream;
	hr = CoCreateInstance(CLSID_SpStream, NULL, CLSCTX_ALL, IID_ISpStream, (void**)&cpStream);
	if(!SUCCEEDED(hr))
		return hr;

	hr = cpStream->SetBaseStream(input, SPDFID_WaveFormatEx, input->cachedWaveFormat);
	if(!SUCCEEDED(hr))
		return hr;

	hr = recognizer->SetInput(cpStream, true);
	if(!SUCCEEDED(hr))
		return hr;

	hr = recoGrammar->SetDictationState(SPRS_ACTIVE);
	if(!SUCCEEDED(hr))
		return hr;

	return hr;
}

int _tmain(int argc, _TCHAR* argv[])
{
	//CComObjectRootEx c;

    if (FAILED(::CoInitialize(NULL)))
        return FALSE;

	WAVEFORMATEX waveFormat;
	WAVEFORMATEX* pWaveFormat = &waveFormat;

	//CComPtr<ISpStreamFormat> cpStreamFormat;
	//pVoice->GetOutputStream(&cpStreamFormat);
	//cpStreamFormat->GetFormat((GUID*)&SPDFID_WaveFormatEx, &pWaveFormat);

	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nChannels = 1;		
	waveFormat.nSamplesPerSec = 48000;
	waveFormat.wBitsPerSample = 16;
	waveFormat.nBlockAlign = (waveFormat.nChannels*waveFormat.wBitsPerSample) / 8 ;
	waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec*waveFormat.nBlockAlign;
	waveFormat.cbSize = 0;

	StreamProvider input;
	input.cachedWaveFormat = pWaveFormat;

	sf::SoundBuffer soundFile;
	soundFile.LoadFromFile("test.wav");
	int size = soundFile.GetSamplesCount();
	short* samples = (short*)soundFile.GetSamples();
	input.SetSample(samples, size);

	SAPINotifySink srSink;

	speechRecognize(&input, &srSink);

	HKEY localMachine = HKEY_LOCAL_MACHINE;
	HKEY groupKey;
	std::wstring tokenGroup(L"SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens");
	LONG result = RegOpenKeyEx(localMachine, tokenGroup.c_str(), 0, KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS, &groupKey);
	if(result==ERROR_SUCCESS)
	{
		int idx = 0;
		do
		{
			DWORD size = 100;
			WCHAR buf[100];			
			result = RegEnumKeyEx(groupKey, idx++, 	buf, &size, 0, NULL, 0, NULL);
			if(result!=ERROR_SUCCESS)
			{
				break;
			}

			HKEY voiceKey;
			result = RegOpenKeyEx(groupKey, buf, 0, KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS, &voiceKey);
			if(result==ERROR_SUCCESS)
			{
				HKEY attributesKey;
				result = RegOpenKeyEx(voiceKey, L"Attributes", 0, KEY_QUERY_VALUE, &attributesKey);
				if(result==ERROR_SUCCESS)
				{
					WCHAR value[1000];				
					size = 1000;
					result = RegGetValue(attributesKey, NULL, L"Name", RRF_RT_ANY, NULL, (PVOID)value, &size);
					if(result==ERROR_SUCCESS)
					{
						std::wstring str(value);
						std::cout << str.c_str() << std::endl;
					}
					else
					{
						std::cout << "Registry contains invalid voice entry, can't open." << std::endl;
					}
				}
				else
				{
					std::cout << "Registry contains invalid voice entry, can't open." << std::endl;
				}
			}
			else
			{
				std::cout << "Registry contains invalid voice entry, can't open." << std::endl;
			}
			std::cout << buf << std::endl;
		} while(result==ERROR_SUCCESS);		
	}

	if(result!=ERROR_SUCCESS)
	{
		WCHAR buf[4096];
		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, result, 0, buf, 4096, NULL);
		std::cout << buf;
	}


	SAPINotifySink sink;
	StreamReceiver streamReceiver;
	HRESULT hr;
    ISpVoice * pVoice = NULL;
    hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
    if( SUCCEEDED( hr ) )
    {
		sink.pVoice = pVoice;
		ULONGLONG myEvents = SPFEI_ALL_TTS_EVENTS;//SPFEI(SPEI_TTS_BOOKMARK)|SPFEI(SPEI_WORD_BOUNDARY)|SPFEI(SPEI_PHONEME)|SPFEI(SPEI_START_INPUT_STREAM)|SPFEI(SPEI_END_INPUT_STREAM);
		//pVoice->SetNotifyCallbackFunction(notifyCallback, (WPARAM)pVoice, 0);
		pVoice->SetInterest(myEvents, myEvents);
		pVoice->SetNotifySink(&sink);
		
		//enum available voices
		ULONG ulCount = 0;
		CComPtr<IEnumSpObjectTokens> cpEnum;
		//hr = SpEnumTokens(SPCAT_VOICES, NULL, NULL, &cpEnum);
		hr = SpEnumTokens(SPCAT_VOICES, L"Name=IVONA 2 Brian", NULL, &cpEnum);
		if(SUCCEEDED(hr))
		{
			hr = cpEnum->GetCount(&ulCount);
		}

		//SpStream stream;
		CComPtr<ISpStream> cpStream;
		CComPtr<IStream> cpMemStream;
		
		HGLOBAL hGlobal = 0;		
		hr = CoCreateInstance(CLSID_SpStream, NULL, CLSCTX_ALL, IID_ISpStream, (void**)&cpStream);
		if(SUCCEEDED(hr))
		{
			//hr = CreateStreamOnHGlobal(hGlobal, true, &cpMemStream);
			cpMemStream = &streamReceiver;
			streamReceiver.cachedWaveFormat = pWaveFormat;

		}
		if(SUCCEEDED(hr))
		{
			hr = cpStream->SetBaseStream(cpMemStream, SPDFID_WaveFormatEx, pWaveFormat);
		}


		if(SUCCEEDED(hr))
		{			
			hr = pVoice->SetOutput(cpStream, true);
			sink.pStream = cpStream;
		}

		//CComPtr<ISpObjectToken> cpAudioOutToken;
		//pVoice->SetOutput(cpAudioOutToken);
		CComPtr<ISpObjectToken> cpVoiceToken;
		//ulCount--;
		//hr = cpEnum->Next(1, &cpVoiceToken, NULL);
		//ulCount--;
		//cpVoiceToken.Release();
		//hr = cpEnum->Next(1, &cpVoiceToken, NULL);

		if(/*SUCCEEDED(hr) &&*/ ulCount--)
		{
			cpVoiceToken.Release();
			hr = cpEnum->Next(1, &cpVoiceToken, NULL);
			if(SUCCEEDED(hr))
			{
				hr = pVoice->SetVoice(cpVoiceToken);
				//pVoice->SetOutput(cpStream, true);
			}
		}
		
		if(SUCCEEDED(hr))
		{
			sink.Speak(L"Initial test for getting started.");
			while(!sink.done)
			{
				Sleep(1);
			}
			streamReceiver.firstWriteTime = 0;


			//hr = pVoice->Speak( L"How are you today?", SPF_DEFAULT, NULL);
			sink.Speak(L"The quick brown fox jumped over the lazy dog!");
		}

		//STATSTG statstg;
		//hr = cpStream->Stat(&statstg, 0);
		//hr = cpStream->Read(


        //hr = pVoice->Speak(L"How are you", 0, NULL);

        // Change pitch
        //hr = pVoice->Speak(L"This sounds normal <pitch middle = '-10'/> but the pitch drops half way through", SPF_IS_XML, NULL );


		//cpStream->GetFormat((GUID*)&SPDFID_WaveFormatEx, &pWaveFormat);


		/*
		ULONG len = 148666;//4096*2*2*2*2;
		ULONG read = 0;
		char* buf = new char[len];
		LARGE_INTEGER li;
		li.HighPart=0;
		li.LowPart=0;
		li.QuadPart=0;
		cpStream->Seek(li, STREAM_SEEK_SET, NULL);
		hr = cpMemStream->Read(buf, len, &read);
		*/

		while(!sink.done)
		{
			Sleep(1);
		}

		std::cout << (streamReceiver.firstWriteTime - sink.startTime) << std::endl;
		std::cout << (sink.stopTime - sink.startTime);

		sf::SoundBuffer soundFile;
		soundFile.LoadFromSamples((const sf::Int16*)&streamReceiver.samples[0], streamReceiver.samples.size(), 1, 48000);
		soundFile.SaveToFile("test.wav");
		sf::Sound sound;
		sound.SetBuffer(soundFile);
		sound.Play();


        pVoice->Release();
        pVoice = NULL;

		if(!SUCCEEDED(hr))
		{
			std::cout << hr;
		}
    }
    ::CoUninitialize();
    return TRUE;

	return 0;
}

